package com.ly.wxstore.comm;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ly.wxstore.web.weixin.WeixinH5PayCallbackController;

/**
 * 微信各种签名工具类
 * 
 * @author Administrator
 *
 */
public class WeixinPaySignUtils {
	
	private static Logger logger = LoggerFactory.getLogger(WeixinPaySignUtils.class);
	
	public static void main(String[] args) {
		/*Map<String, String> params = new HashMap<String, String>();
		params.put("appid", "wx8888888888888888");
		params.put("mch_id", "1900000109");
		params.put("device_info", "013467007045764");
		params.put("body", "");
		params.put("nonce_str", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS");

		System.out.println(getXmlStr(params,null));*/
		
		String str = "hello1234";
		System.out.println(getMD5_2(str));
		System.out.println(getMD5(str));

	}
	
	/**
	 * 根据参数生成最终的提交给微信支付接口的xml，此xml中包含sign字段。
	 * @param params
	 * @return
	 */
	public static String getXmlStr(Map<String, String> params, String payApiKey){
		StringBuffer xmlStr = new StringBuffer();
		
		xmlStr.append("<xml>\n");
		Set<String> keys = params.keySet();
		for (String key : keys) {
			xmlStr.append("    <").append(key).append("><![CDATA[").append(params.get(key)).append("]]></").append(key).append(">\n");
		}
		xmlStr.append("    <sign><![CDATA[").append(payUniSign(params,payApiKey)).append("]]></sign>\n");
		
		xmlStr.append("</xml>");
		return xmlStr.toString();
	}

	/**
	 * <h3>微信支付签名算法</h3>
	 * <P>
	 * 签名生成的通用步骤如下：
	 * </p>
	 * <p>
	 * 
	 * 第一步，设所有发送或者接收到的数据为集合M，将集合M内非空参数值的参数按照参数名ASCII码从小到大排序（字典序），使用URL键值对的格式（
	 * 即key1=value1&key2=value2…）拼接成字符串stringA。 特别注意以下重要规则：
	 * <ol>
	 * <li>◆ 参数名ASCII码从小到大排序（字典序）；</li>
	 * <li>◆ 如果参数的值为空不参与签名；</li>
	 * <li>◆ 参数名区分大小写；</li>
	 * <li>◆ 验证调用返回或微信主动通知签名时，传送的sign参数不参与签名，将生成的签名与该sign值作校验。</li>
	 * <li>◆ 微信接口可能增加字段，验证签名时必须支持增加的扩展字段</li>
	 * </ol>
	 * </p>
	 * <p>
	 * 第二步，在stringA最后拼接上key得到stringSignTemp字符串，并对stringSignTemp进行MD5运算，
	 * 再将得到的字符串所有字符转换为大写，得到sign值signValue。
	 * key设置路径：微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
	 * </p>
	 * 
	 * @return 参数签名：sign
	 */
	public static String payUniSign(Map<String, String> params, String payApiKey) {
		String sign = "";
		
		String strTmp ="";
		if(StringUtils.isNotBlank(payApiKey)){
			 strTmp = sortToString(params)+"&key="+payApiKey;
		}else{
			strTmp = sortToString(params);
		}
		
		
		logger.info("#1.生成字符串：#2.连接商户key："+strTmp);
		sign = getMD5_2(strTmp);
		sign = sign.toUpperCase();
		logger.info("#3.md5编码并转成大写："+sign);
		
		return sign;
	}


	/**
	 * 去除空值和sign参数，进行字典序排序，然后用&拼接
	 * 
	 * @param params
	 * @return
	 */
	private static String sortToString(Map<String, String> params) {

		// 移除空值 和 sign参数
		removeBlankValue(params);

		// key排序
		Set<String> keys = params.keySet();

		String[] sortedKeys = toArray(keys);
		Arrays.sort(sortedKeys);

		//
		StringBuffer str = new StringBuffer();

		for (int i = 0; i < sortedKeys.length; i++) {
			String key = sortedKeys[i];
			String value = params.get(key);
			str.append(key + "=" + value + "&");
		}

		str.replace(str.lastIndexOf("&"), str.length(), "");

		return str.toString();
	}

	private static String[] toArray(Set<String> keys) {
		String[] sortedKeys = new String[keys.size()];

		int i = 0;
		for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {
			String key = (String) iterator.next();
			sortedKeys[i] = key;
			i++;
		}
		return sortedKeys;
	}

	/**
	 * 移除空值 和 sign参数
	 * 
	 * @param params
	 */
	private static void removeBlankValue(Map<String, String> params) {
		//
		if(params.containsKey("sign")){
			params.remove("sign");
		}
		
		Set<String> keys = params.keySet();
		for (Iterator<String> keyIter = keys.iterator(); keyIter.hasNext();) {
			String key = keyIter.next();
			String value = params.get(key);
			if (StringUtils.isBlank(value)) {
				params.remove(key);
			}
		}
	}
	
	/**
	 * MD5加密 该算法实现由Bug，如果MD5加密有的结果第一位是0,则0会被去掉，原因就是BigInteger！！！！
	 * @param input
	 * @return
	 */
	@Deprecated
	public static String getMD5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] messageDigest = md.digest(input.getBytes());
            BigInteger number = new BigInteger(1, messageDigest);
            String hashtext = number.toString(16);
            // Now we need to zero pad it if you actually want the full 32 chars.
           /* while (hashtext.length() < 32) {
                hashtext = "0" + hashtext;
            }*/
            return hashtext;
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }	
	
	//静态方法，便于作为工具类  
    public static String getMD5_2(String plainText) {  
        try {  
            MessageDigest md = MessageDigest.getInstance("MD5");  
            md.update(plainText.getBytes());  
            byte b[] = md.digest();  
  
            int i;  
  
            StringBuffer buf = new StringBuffer("");  
            for (int offset = 0; offset < b.length; offset++) {  
                i = b[offset];  
                if (i < 0)  
                    i += 256;  
                if (i < 16)  
                    buf.append("0");  
                buf.append(Integer.toHexString(i));  
            }  
            //32位加密  
            return buf.toString();  
            // 16位的加密  
            //return buf.toString().substring(8, 24);  
        } catch (NoSuchAlgorithmException e) {  
            e.printStackTrace();  
            return null;  
        }  
  
    }  

}
